/*
 * Copyright (c) 2009-2010 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.rpgmapper.tools;

import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.texture.Image.Format;
import java.nio.ByteBuffer;
import java.util.EnumMap;

public class ImageUtils {

    private static final EnumMap<Format, DecodeParams> params = new EnumMap<Format, DecodeParams>(Format.class);

    private static class DecodeParams {

        final int bpp, am, rm, gm, bm, as, rs, gs, bs, im, is, ra, rg, rb, rr;

        public DecodeParams(int bpp, int am, int rm, int gm, int bm, int as, int rs, int gs, int bs, int im, int is) {
            this.bpp = bpp;
            this.am = am;
            this.rm = rm;
            this.gm = gm;
            this.bm = bm;
            this.as = as;
            this.rs = rs;
            this.gs = gs;
            this.bs = bs;
            this.im = im;
            this.is = is;
            ra = 8 - Integer.bitCount(am);
            rr = 8 - Integer.bitCount(rm);
            rg = 8 - Integer.bitCount(gm);
            rb = 8 - Integer.bitCount(bm);

        }

        public DecodeParams(int bpp, int rm, int rs, int im, int is, boolean alpha) {
            this.bpp = bpp;
            if (alpha) {
                this.am = rm;
                this.as = rs;
                this.rm = 0;
                this.rs = 0;
            } else {
                this.rm = rm;
                this.rs = rs;
                this.am = 0;
                this.as = 0;
            }

            this.gm = 0;
            this.bm = 0;
            this.gs = 0;
            this.bs = 0;
            this.im = im;
            this.is = is;
            ra = 8 - Integer.bitCount(am);
            rr = 8 - Integer.bitCount(rm);
            rg = 8 - Integer.bitCount(gm);
            rb = 8 - Integer.bitCount(bm);
        }

        public DecodeParams(int bpp, int rm, int rs, int im, int is) {
            this(bpp, rm, rs, im, is, false);
        }
    }

    static {
        final int mx___ = 0xff000000;
        final int m_x__ = 0x00ff0000;
        final int m__x_ = 0x0000ff00;
        final int m___x = 0x000000ff;
        final int sx___ = 24;
        final int s_x__ = 16;
        final int s__x_ = 8;
        final int s___x = 0;
        final int mxxxx = 0xffffffff;
        final int sxxxx = 0;

        final int m4x___ = 0xf000;
        final int m4_x__ = 0x0f00;
        final int m4__x_ = 0x00f0;
        final int m4___x = 0x000f;
        final int s4x___ = 12;
        final int s4_x__ = 8;
        final int s4__x_ = 4;
        final int s4___x = 0;

        final int m5___ = 0xf800;
        final int m_5__ = 0x07c0;
        final int m__5_ = 0x003e;
        final int m___1 = 0x0001;

        final int s5___ = 11;
        final int s_5__ = 6;
        final int s__5_ = 1;
        final int s___1 = 0;

        final int m5__ = 0xf800;
        final int m_6_ = 0x07e0;
        final int m__5 = 0x001f;

        final int s5__ = 11;
        final int s_6_ = 5;
        final int s__5 = 0;

        final int mxx__ = 0xffff0000;
        final int sxx__ = 32;
        final int m__xx = 0x0000ffff;
        final int s__xx = 0;

        // note: compressed, depth, or floating point formats not included here..

        params.put(Format.ABGR8, new DecodeParams(4, mx___, m___x, m__x_, m_x__,
                sx___, s___x, s__x_, s_x__,
                mxxxx, sxxxx));
        params.put(Format.ARGB4444, new DecodeParams(2, m4x___, m4_x__, m4__x_, m4___x,
                s4x___, s4_x__, s4__x_, s4___x,
                mxxxx, sxxxx));
        params.put(Format.Alpha16, new DecodeParams(2, mxxxx, sxxxx, mxxxx, sxxxx, true));
        params.put(Format.Alpha8, new DecodeParams(1, mxxxx, sxxxx, mxxxx, sxxxx, true));
        params.put(Format.BGR8, new DecodeParams(3, 0, m___x, m__x_, m_x__,
                0, s___x, s__x_, s_x__,
                mxxxx, sxxxx));
        params.put(Format.Luminance16, new DecodeParams(2, mxxxx, sxxxx, mxxxx, sxxxx, false));
        params.put(Format.Luminance8, new DecodeParams(1, mxxxx, sxxxx, mxxxx, sxxxx, false));
        params.put(Format.Luminance16Alpha16, new DecodeParams(4, m__xx, mxx__, 0, 0,
                s__xx, sxx__, 0, 0,
                mxxxx, sxxxx));
        params.put(Format.Luminance16F, new DecodeParams(2, mxxxx, sxxxx, mxxxx, sxxxx, false));
        params.put(Format.Luminance16FAlpha16F, new DecodeParams(4, m__xx, mxx__, 0, 0,
                s__xx, sxx__, 0, 0,
                mxxxx, sxxxx));
        params.put(Format.Luminance32F, new DecodeParams(4, mxxxx, sxxxx, mxxxx, sxxxx, false));
        params.put(Format.Luminance8, new DecodeParams(1, mxxxx, sxxxx, mxxxx, sxxxx, false));
        params.put(Format.RGB5A1, new DecodeParams(2, m___1, m5___, m_5__, m__5_,
                s___1, s5___, s_5__, s__5_,
                mxxxx, sxxxx));
        params.put(Format.RGB565, new DecodeParams(2, 0, m5__, m_6_, m__5,
                0, s5__, s_6_, s__5,
                mxxxx, sxxxx));
        params.put(Format.RGB8, new DecodeParams(3, 0, m_x__, m__x_, m___x,
                0, s_x__, s__x_, s___x,
                mxxxx, sxxxx));
        params.put(Format.RGBA8, new DecodeParams(4, m___x, mx___, m_x__, m__x_,
                s___x, sx___, s_x__, s__x_,
                mxxxx, sxxxx));
    }

    private static int Ix(int x, int y, int w) {
        return y * w + x;
    }

    private static int readPixel(ByteBuffer buf, int idx, int bpp) {
        buf.position(idx);
        int original = buf.get() & 0xff;
        while ((--bpp) > 0) {
            original = (original << 8) | (buf.get() & 0xff);
        }
        return original;
    }

    private static void writePixel(ByteBuffer buf, int idx, int pixel, int bpp) {
        buf.position(idx);
        while ((--bpp) >= 0) {
//            pixel = pixel >> 8;
            byte bt = (byte) ((pixel >> (bpp * 8)) & 0xff);
//            buf.put( (byte) (pixel & 0xff) );
            buf.put(bt);
        }
    }

    /**
     * Convert an ColorRBGA array image to jME image.
     */
    public static void convert(ColorRGBA[] image, Format format, ByteBuffer buf) {
        DecodeParams p = params.get(format);
        if (p == null) {
            throw new UnsupportedOperationException("Image format " + format + " is not supported");
        }

        for (int x = 0; x < image.length; x++) {

            int outputPixel = encodeColor(image[x], p);
            int i = (x * p.bpp);
            writePixel(buf, i, outputPixel, p.bpp);
        }

    }

    /**
     * Convert an ColorRBGA array image to jME image.
     */
    public static void clear(ColorRGBA color, Format format, ByteBuffer buf, int size) {
        DecodeParams p = params.get(format);
        if (p == null) {
            throw new UnsupportedOperationException("Image format " + format + " is not supported");
        }

        for (int x = 0; x < size * size; x++) {

            int outputPixel = encodeColor(color, p);
            int i = (x * p.bpp);
            writePixel(buf, i, outputPixel, p.bpp);
        }

    }

    /**
     * Convert an ColorRBGA array image to jME image.
     */
    public static void draw(Vector2f pixel, ColorRGBA color, Format format, ByteBuffer buf, int size) {
        DecodeParams p = params.get(format);
        if (p == null) {
            throw new UnsupportedOperationException("Image format " + format + " is not supported");
        }

        int x = (int) (pixel.x * size);
        int y = (int) (pixel.y * size);
        x += y * size;

        int outputPixel = encodeColor(color, p);
        int i = (x * p.bpp);
        writePixel(buf, i, outputPixel, p.bpp);

    }

    /**
     * Convert an ColorRBGA array image to jME image.
     */
    public static void drawLine(Vector2f start, Vector2f end, ColorRGBA color, Format format, ByteBuffer buf, int size) {
        DecodeParams p = params.get(format);
        if (p == null) {
            throw new UnsupportedOperationException("Image format " + format + " is not supported");
        }
        int outputPixel = encodeColor(color, p);

        int x0 = (int) Math.round(start.x * size);
        int y0 = (int) Math.round(start.y * size);
        int x1 = (int) Math.round(end.x * size);
        int y1 = (int) Math.round(end.y * size);

        int dx = Math.abs(x1 - x0);
        int dy = Math.abs(y1 - y0);
        int sx = -1;
        int sy = -1;
        if (x0 < x1) {
            sx = 1;
        }
        if (y0 < y1) {
            sy = 1;
        }
        int err = dx - dy;

        while (x0 != x1 || y0 != y1) {

            writePixel(buf, (x0 + y0 * size) * p.bpp, outputPixel, p.bpp);
            int e2 = 2 * err;
            if (e2 > -dy) {
                err = err - dy;
                x0 = x0 + sx;
            }
            if (e2 < dx) {
                err = err + dx;
                y0 = y0 + sy;
            }
        }


    }

    private static int encodeColor(ColorRGBA color, DecodeParams p) {
        // Get ARGB
        int argb = color.asIntARGB();
        // Extract color components
        int a = (argb & 0xff000000) >> 24;
        int r = (argb & 0x00ff0000) >> 16;
        int g = (argb & 0x0000ff00) >> 8;
        int b = (argb & 0x000000ff);
        // Remove anything after 8 bits
        a = a & 0xff;
        r = r & 0xff;
        g = g & 0xff;
        b = b & 0xff;
        // Do bit reduction, assumes proper rounding has already been
        // done.
        a = a >> p.ra;
        r = r >> p.rr;
        g = g >> p.rg;
        b = b >> p.rb;
        // Put components into appropriate positions
        a = (a << p.as) & p.am;
        r = (r << p.rs) & p.rm;
        g = (g << p.gs) & p.gm;
        b = (b << p.bs) & p.bm;
        int outputPixel = ((a | r | g | b) << p.is) & p.im;
        return outputPixel;
    }
}
